import React, { useState, useEffect } from 'react';
import {
  Divider,
  MenuItem,
  TextField,
  Popover,
  ListItem,
  ListItemButton,
  ListItemIcon,
  ListItemText,
  List,
} from '@mui/material';
import { Check as CheckIcon } from '@mui/icons-material';
import NetPyNEField from '../../general/NetPyNEField';
import Utils from '../../../utils/Utils';

interface NetPyNEIncludeProps {
  id: string;
  model: string;
  defaultOptions?: string[];
  initialValue?: string;
  updates?: any;
  [key: string]: any;
}

interface IncludeData {
  gids: number[];
  groups: string[];
  popids: Record<string, number[]>;
  exclusive: boolean | string;
}

/**
 * NetPyNEInclude Component
 * Component for selecting which cells/populations to include in plots
 */
const NetPyNEInclude: React.FC<NetPyNEIncludeProps> = ({
  id,
  model,
  defaultOptions = ['all', 'allCells', 'allNetStims'],
  initialValue = 'all',
  updates,
  ...otherProps
}) => {
  const getDataTemplate = (): IncludeData => ({
    gids: [],
    groups: [],
    popids: {},
    exclusive: false,
  });

  const [include, setInclude] = useState<IncludeData>(getDataTemplate());
  const [mainPopoverOpen, setMainPopoverOpen] = useState(false);
  const [secondPopoverOpen, setSecondPopoverOpen] = useState<Record<string, boolean>>({});
  const [label, setLabel] = useState('');
  const [data, setData] = useState<any>(null);
  const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
  const [anchorEl2, setAnchorEl2] = useState<HTMLElement | null>(null);

  useEffect(() => {
    collectInfo();
  }, []);

  useEffect(() => {
    if (updates !== undefined) {
      collectInfo();
    }
  }, [updates]);

  const checkEqual = (a: Record<string, boolean>, b: Record<string, boolean>): boolean => {
    const aProps = Object.getOwnPropertyNames(a);
    const bProps = Object.getOwnPropertyNames(b);
    if (aProps.length !== bProps.length) {
      return false;
    }
    for (let i = 0; i < aProps.length; i++) {
      const propName = aProps[i];
      if (a[propName] !== b[propName]) {
        return false;
      }
    }
    return true;
  };

  const whoIsIncluded = (includeData: IncludeData, dataObj: any): string => {
    let pops = 0;
    let cells = 0;
    let answer = '';

    if (includeData.exclusive) {
      return `${includeData.exclusive} -- ${dataObj.gids} cells -- all NetStims`;
    }
    if (includeData.groups.indexOf('allCells') > -1) {
      if (includeData.groups.indexOf('allNetStims') === -1) {
        return `allCells -- ${dataObj.gids} cells`;
      }
      return `all -- ${dataObj.gids} cells -- all NetStims`;
    }

    includeData.groups.forEach((group) => {
      if (group !== 'allNetStims') {
        pops += 1;
        cells += dataObj[group];
      }
    });

    cells += includeData.gids.length;

    Object.keys(includeData.popids).forEach((key) => {
      if (includeData.popids[key].length > 0) {
        cells += includeData.popids[key].length;
        pops += 1;
      }
    });

    if (pops > 0) {
      answer += `${pops} pops -- `;
    }
    answer += `${cells} cells `;
    if (includeData.groups.indexOf('allNetStims') > -1) {
      answer += ' -- all netStims';
    }
    return answer;
  };

  const sendToPython = (): void => {
    const dataArray: any[] = [];
    if (include.exclusive) {
      dataArray.push(include.exclusive);
    } else {
      include.groups.forEach((group) => dataArray.push(group));
      include.gids.forEach((gid) => dataArray.push(gid));
      Object.keys(include.popids).forEach((key) => dataArray.push([key, include.popids[key]]));
    }
    Utils.execPythonMessage(`netpyne_geppetto.${model} = ${JSON.stringify(dataArray)}`);
  };

  const convertFromPython = (pythonData: any[]): IncludeData => {
    const out = getDataTemplate();
    pythonData.forEach((element) => {
      if (typeof element === 'number') {
        out.gids.push(element);
      } else if (typeof element === 'string') {
        element !== 'all' ? out.groups.push(element) : (out.exclusive = 'all');
      } else if (Array.isArray(element)) {
        if (typeof element[1] === 'number') {
          out.popids[element[0]] = [element[1]];
        } else {
          out.popids[element[0]] = element[1];
        }
      }
    });
    return out;
  };

  const collectInfo = async (): Promise<void> => {
    try {
      const numberOfCellsByPopulation = await Utils.evalPythonMessage('netpyne_geppetto.getGIDs', []);
      if (numberOfCellsByPopulation) {
        const modelKey = model.split("'")[1];
        const dataInPythonFormat = await Utils.evalPythonMessage(
          'netpyne_geppetto.getInclude',
          [modelKey]
        );
        let included: IncludeData;
        if (dataInPythonFormat) {
          included = convertFromPython(dataInPythonFormat);
        } else {
          included = convertFromPython([initialValue]);
        }

        const clone = { ...numberOfCellsByPopulation };
        Object.keys(clone).forEach((key) => {
          clone[key] = false;
        });

        setInclude(included);
        setSecondPopoverOpen(clone);
        setData(numberOfCellsByPopulation);
        setLabel(whoIsIncluded(included, numberOfCellsByPopulation));
      }
    } catch (error) {
      console.error('Error collecting info:', error);
    }
  };

  const handleMainPopoverOpen = (open: boolean, preventDefault: boolean = false, target?: HTMLElement): void => {
    const clone = { ...secondPopoverOpen };
    Object.keys(clone).forEach((key) => {
      clone[key] = false;
    });

    if ((open || mainPopoverOpen) && !(open && mainPopoverOpen)) {
      setMainPopoverOpen(open);
      setSecondPopoverOpen(clone);
      if (target) {
        setAnchorEl(target);
      }
    }

    if (!open) {
      sendToPython();
      setLabel(whoIsIncluded(include, data));
    }
  };

  const handleSecondPopoverOpen = (name: string, open: boolean, preventDefault: boolean = false, target?: HTMLElement): void => {
    const clone = { ...secondPopoverOpen };
    Object.keys(clone).forEach((key) => {
      clone[key] = key === name ? open : false;
    });

    if (!checkEqual(clone, secondPopoverOpen)) {
      setSecondPopoverOpen(clone);
      if (target) {
        setAnchorEl2(target);
      }
    }
  };

  const closeSecondPopover = (): void => {
    const clone = { ...secondPopoverOpen };
    Object.keys(clone).forEach((key) => {
      clone[key] = false;
    });
    if (!checkEqual(clone, secondPopoverOpen)) {
      setSecondPopoverOpen(clone);
    }
  };

  const handleMainMenusClick = (name: string, group: 'groups' | 'exclusive'): void => {
    let newInclude: IncludeData;

    if (name === 'all') {
      newInclude = getDataTemplate();
      newInclude.exclusive = 'all';
    } else if (name === 'allCells') {
      newInclude = getDataTemplate();
      newInclude.groups = ['allCells'];
      if (include.groups.indexOf('allNetStims') > -1) {
        newInclude.groups.push('allNetStims');
      }
    } else {
      newInclude = { ...include };
      const groupValue = newInclude[group];
      const index = Array.isArray(groupValue) ? groupValue.indexOf(name) : -1;
      if (index === -1) {
        (newInclude[group] as any[]).push(name);
      } else {
        (newInclude[group] as any[]).splice(index, 1);
      }
      newInclude.exclusive = false;
      if (name in newInclude.popids) {
        delete newInclude.popids[name];
      }
      if (include.groups.indexOf('allCells') > -1 && name !== 'allNetStims') {
        newInclude.groups.splice(newInclude.groups.indexOf('allCells'), 1);
      }
    }

    setInclude(newInclude);
  };

  const handleSecondaryMenusClick = (group: 'gids' | 'popids', name: string, item: number): void => {
    const newInclude = { ...include };

    if (group === 'gids') {
      const index = newInclude.gids.indexOf(item);
      if (index === -1) {
        newInclude.gids.push(item);
      } else {
        newInclude.gids.splice(index, 1);
      }
    } else if (group === 'popids') {
      if (name in newInclude.popids) {
        const index = newInclude.popids[name].indexOf(item);
        if (index === -1) {
          newInclude.popids[name].push(item);
        } else {
          newInclude.popids[name].splice(index, 1);
        }
      } else {
        newInclude.popids[name] = [item];
      }
      if (newInclude.groups.indexOf(name) > -1) {
        newInclude.groups.splice(newInclude.groups.indexOf(name), 1);
      }
    }

    newInclude.exclusive = false;
    if (newInclude.groups.indexOf('allCells') > -1 && name !== 'allNetStims') {
      newInclude.groups.splice(newInclude.groups.indexOf('allCells'), 1);
    }

    setInclude(newInclude);
  };

  const IsSecondaryMenuChecked = (group: 'gids' | 'popids', name: string, index: number): boolean => {
    if (group === 'gids') {
      return include.gids.indexOf(index) > -1;
    }
    if (group === 'popids') {
      if (name in include.popids) {
        return include.popids[name].indexOf(index) > -1;
      }
      return false;
    }
    return false;
  };

  const defaultMenus = (): React.ReactNode[] => {
    return defaultOptions.map((name) => (
      <MenuItem
        key={name}
        value={name}
        onClick={() => handleMainMenusClick(name, name === 'all' ? 'exclusive' : 'groups')}
        selected={!!(include.exclusive === name || include.groups.indexOf(name) > -1)}
        onMouseEnter={() => closeSecondPopover()}
      >
        {name}
      </MenuItem>
    ));
  };

  const variableMenus = (name: string, size: number): React.ReactNode => {
    const menuItems = Array.from(Array(size).keys()).map((index) => (
      <ListItemButton
        key={name + index}
        onClick={() => handleSecondaryMenusClick(name === 'gids' ? 'gids' : 'popids', name, index)}
        selected={IsSecondaryMenuChecked(name === 'gids' ? 'gids' : 'popids', name, index)}
      >
        <ListItemIcon>
          {IsSecondaryMenuChecked(name === 'gids' ? 'gids' : 'popids', name, index) ? (
            <CheckIcon />
          ) : (
            <span />
          )}
        </ListItemIcon>
        <ListItemText>{`cell ${index}`}</ListItemText>
      </ListItemButton>
    ));

    return (
      <div key={`${name}div`}>
        <MenuItem
          key={name}
          value={name}
          selected={name !== 'gids' ? include.groups.indexOf(name) > -1 : false}
          onClick={() => {
            if (name !== 'gids') {
              handleMainMenusClick(name, 'groups');
            }
          }}
          onMouseEnter={(e) => handleSecondPopoverOpen(name, true, false, e.currentTarget as HTMLElement)}
        >
          {name}
        </MenuItem>
        <Popover
          style={{
            height: size < 6 ? 48 * size : 240,
            width: 200,
          }}
          key={`${name}Popover`}
          open={secondPopoverOpen ? secondPopoverOpen[name] || false : false}
          anchorEl={anchorEl2}
          anchorOrigin={{
            horizontal: 'right',
            vertical: 'top',
          }}
          transformOrigin={{
            horizontal: 'left',
            vertical: 'top',
          }}
          onClose={() => closeSecondPopover()}
          onMouseLeave={() => closeSecondPopover()}
        >
          <List style={{ width: 200 }} dense>
            {menuItems}
          </List>
        </Popover>
      </div>
    );
  };

  const otherMenus = (): React.ReactNode[] => {
    const menuItems: React.ReactNode[] = [];
    if (data) {
      for (const key in data) {
        if (key !== 'gids') {
          menuItems.push(variableMenus(key, data[key]));
        }
      }
    }
    return menuItems;
  };

  return (
    <div>
      <NetPyNEField id={id}>
        <TextField
          fullWidth
          variant="filled"
          label="Include in the plot"
          value={label}
          onClick={(e) => handleMainPopoverOpen(true, false, e.currentTarget as HTMLElement)}
        />
      </NetPyNEField>
      <Popover
        open={mainPopoverOpen}
        anchorEl={anchorEl}
        onClose={() => handleMainPopoverOpen(false)}
        anchorOrigin={{
          horizontal: 'left',
          vertical: 'bottom',
        }}
        transformOrigin={{
          horizontal: 'left',
          vertical: 'top',
        }}
      >
        {defaultMenus()}
        <Divider />
        {data && variableMenus('gids', data.gids)}
        <Divider />
        {otherMenus()}
      </Popover>
    </div>
  );
};

export default NetPyNEInclude;

